﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.IO;
using Semaphore = Utilities.Semaphore;
using System.Diagnostics;

namespace DownloadAccelerator
{
    public partial class mainForm : Form
    {
        private Thread[] _thrDownloader; //for downloading file
        private Thread _thrDisplayer; //for displaying process
        private Thread _thrJoiner; //for joining file at the end

        private Semaphore[] _sem;

        private string _downloadPath = @"http://www.sneakysexvids.com/clips/364.flv";
        private string _savePath = @"F:\Desktop\SWINBURNE\HIT3197 Advanced .NET\DownloadAccelerator\03.Maria.flv";
        
        private int _numOfThreads = 2;
        private int _bufferSize = 2048;

        private long _fileSize = 0;

        private DataStorage _dataStorage;

        private Stopwatch _timer;

        private bool _downloading = false;

        public mainForm()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            new Thread(StartDownloading).Start();
        }

        private void StartDownloading()
        {
            //validate input values

            //get download file size
            long fileSize = 0;
            try
            {
                fileSize = GetFileSize(_downloadPath);
            }
            catch (WebException)
            {
                MessageBox.Show("Cannot connect. ");
                return;
            }

            _downloading = true;

            _timer = new Stopwatch();
            _timer.Start();

            this.Invoke(new SetTextCallBack(this.SetText), new object[] { txtProcess, "Initiliazing" });

            //get download path
            _downloadPath = txtDownloadFrom.Text;
            if (_downloadPath == "")
                _downloadPath = @"http://www.sneakysexvids.com/clips/364.flv";

            //get save path
            //savePath = txtSaveTo.Text;

            //get number of threads
            _numOfThreads = Convert.ToInt16(nudNumberOfThreads.Value);

            //set maximum connection
            ServicePointManager.DefaultConnectionLimit = _numOfThreads;

            Uri uri = new Uri(_downloadPath);
            ServicePoint sp = ServicePointManager.FindServicePoint(uri);
            sp.ConnectionLimit = _numOfThreads;

            //comment here
            _dataStorage = new DataStorage(_numOfThreads);

            //create threads
            _thrDownloader = new Thread[_numOfThreads];
            _thrDisplayer = new Thread(DisplayProcess);
            _thrJoiner = new Thread(JoinFile);

            _sem = new Semaphore[_numOfThreads];

            for (int i = 0; i < _numOfThreads; i++)
            {
                int index = i;

                _sem[index] = new Semaphore(0);

                long startPoint = GetStartPoint(fileSize, index);
                long endPoint = GetEndPoint(fileSize, index);
                _thrDownloader[index] = new Thread(new ThreadStart(delegate() { Download(_downloadPath, _savePath + index, startPoint, endPoint, index); }));
                _thrDownloader[index].Start();
            }

            //disable controls

            //start threads
            _thrDisplayer.Start();
            _thrJoiner.Start();

            //enable controls
        }

        private long GetEndPoint(long fileSize, int i)
        {
            long chunkSize = GetChunkSize(fileSize);

            long endPoint = chunkSize * (i + 1) - 1;

            if (endPoint > fileSize)
                endPoint = fileSize - 1;

            return endPoint;
        }

        private long GetChunkSize(long fileSize)
        {
             return (long)(Math.Round((double)fileSize / _numOfThreads));
        }

        private long GetStartPoint(long fileSize, int i)
        {
            long chunkSize = GetChunkSize(fileSize);

            return chunkSize * i;
        }

        private long GetFileSize(string downloadPath)
        {
            try
            {
                // Create a request to the file we are downloading
                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(downloadPath);

                // Set default authentication for retrieving the file
                webRequest.Credentials = CredentialCache.DefaultCredentials;

                // Retrieve the response from the server
                HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();

                // Ask the server for the file size and store it
                _fileSize = webResponse.ContentLength;

                webResponse.Close();
                webRequest.Abort();

                return _fileSize;
            }
            catch (WebException)
            {
                throw;
            }
        }

        private void Download(string downloadPath, string savePath, long startPoint, long endPoint, int index)
        {
            HttpWebRequest webRequest = null;
            HttpWebResponse webResponse = null;

            Stream strResponse = null;
            Stream strLocal = null;

            try
            {
                _dataStorage.Set(index, "Connecting");

                // Put the object argument into an int variable
                int startPointInt = Convert.ToInt32(startPoint);

                // Get size of data needed to download
                long downloadSize = endPoint - startPoint + 1;

                // Create a request to the file we are downloading
                webRequest = (HttpWebRequest)WebRequest.Create(downloadPath);

                // Set the starting point of the request
                webRequest.AddRange(startPointInt);

                // Set default authentication for retrieving the file
                webRequest.Credentials = CredentialCache.DefaultCredentials;

                // Retrieve the response from the server
                webResponse = (HttpWebResponse)webRequest.GetResponse();

                // Open the URL for download
                strResponse = webResponse.GetResponseStream();

                // Create a new file stream where we will be saving the data (local drive)
                //if (startPointInt == 0)
                //{
                strLocal = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None);
                //}
                //else
                //{
                //strLocal = new FileStream(savePath, FileMode.Append, FileAccess.Write, FileShare.None);
                //}

                // It will store the current number of bytes we retrieved from the server
                int bytesSize = 0;

                // A buffer for storing and writing the data retrieved from the server
                byte[] downBuffer = new byte[_bufferSize];

                // Loop through the buffer until the buffer is empty
                while ((bytesSize = strResponse.Read(downBuffer, 0, downBuffer.Length)) > 0)
                {
                    // Write the data from the buffer to the local hard drive
                    if (strLocal.Length + bytesSize < downloadSize)
                    {
                        strLocal.Write(downBuffer, 0, bytesSize);
                    }
                    else
                    {
                        strLocal.Write(downBuffer, 0, Convert.ToInt16(downloadSize - strLocal.Length));
                        break;
                    }

                    _dataStorage.Set(index, (strLocal.Length + bytesSize) + " / " + downloadSize);
                }
                _dataStorage.Set(index, "Done");
            }
            catch(Exception)
            {
                _dataStorage.Set(index, "Error");
            }
            finally
            {
                // When the above code has ended, close the streams
                if(strLocal != null) strLocal.Close();
                if (strResponse != null) strResponse.Close();

                if (webResponse != null) webResponse.Close();
                if (webRequest != null) webRequest.Abort();
                
                _sem[index].Release();
            }
        }

        private void DisplayProcess()
        {
            string sizeInString = ToSizeInString(_fileSize);

            while (_downloading)
            {
                String text = "File size: " + sizeInString + "\r\n";
                //text += "Completed: Not Available yet\r\n";
                //text += "Percent: Not Available yet\r\n";
                text += "Elapsed: " + _timer.Elapsed.Duration().ToString() + "\r\n";
                //text += "Left: Not Available yet\r\n";
                //text += "Speed: Not Available yet\r\n";

                for (int i = 0; i < _numOfThreads; i++)
                {
                    text += "Thread " + (i + 1) + ": " + _dataStorage.Get(i) + "\r\n";
                }

                this.Invoke(new SetTextCallBack(this.SetText), new object[] { txtProcess, text });

                Thread.Sleep(100);
            }
        }

        private String ToSizeInString(double sizeInBytes)
        {
            String sizeInString = "";

            // greater than 1 MB
            if (sizeInBytes > (1024 ^ 2))
            {
                sizeInString = Math.Round(sizeInBytes / (1024 ^ 2), 2).ToString() + " MBs";
            }
            else if (sizeInBytes > 1024) //greate than 1 KB
            {
                sizeInString = Math.Round(sizeInBytes / 1024, 2).ToString() + " KBs";
            }
            else
            {
                sizeInString = sizeInBytes.ToString() + " Bytes";
            }

            return sizeInString;
        }

        private delegate void SetTextCallBack(TextBox txtBox, String text);

        private void SetText(TextBox txtBox, String text)
        {
            txtBox.Text = text;

            txtBox.SelectionStart = txtBox.Text.Length;
            txtBox.ScrollToCaret();
        }

        private void AddText(TextBox txtBox, String text)
        {
            txtBox.Text += text;

            txtBox.SelectionStart = txtBox.Text.Length;
            txtBox.ScrollToCaret();
        }

        private void JoinFile()
        {
            FileStream fsSource = null;
            string message = "";

            try
            {
                if (File.Exists(_savePath))
                    File.Delete(_savePath);
            }
            catch { }

            try
            {
                fsSource = new FileStream(_savePath, FileMode.Append);

                for (int i = 0; i < _numOfThreads; i++)
                {
                    //_sem[i].Acquire();
                    bool acquired = false;

                    while (acquired == false)
                    {
                        acquired = _sem[i].TryAcquire(1000);
                    }

                    if (_dataStorage.Get(i).ToString() == "Error")
                    {
                        throw new Exception("Cannot download file.");
                    }

                    Byte[] bytePart = System.IO.File.ReadAllBytes(_savePath + i);
                    fsSource.Write(bytePart, 0, bytePart.Length);

                    try
                    {
                        File.Delete(_savePath + i);
                    }
                    catch (Exception ex)
                    {
                        WriteLog("Error while deleting file " + i + ": " + ex.ToString());
                    }
                }

                message = "Download completed";
            }
            catch (ThreadInterruptedException)
            {
                message = "Thread Interrupted. Please try again";
            }
            catch (Exception ex)
            {
                message = ex.Message + "Please try again";
            }
            finally
            {
                if(fsSource != null)
                    fsSource.Close();
                
                _timer.Stop();

                _downloading = false;
            }

            MessageBox.Show(message);
        }

        //private TextWriter _writer;
        private void WriteLog(string p)
        {
            //lock (_writer)
            //{
            //    _writer = new StreamWriter("log.txt", true);
            //    _writer.WriteLine(p);
            //    _writer.Close();
            //}
        }
    }
}
